package com.simple.slider.puzzle;

import java.util.Random;

import android.util.Log;

public class PuzzleBoard {
	private static final String LOG_TAG = PuzzleBoard.class.getName();
	private static final int SHUFFLE_MULTIPLIER = 1;
	
	private static final int UP = 0;
	private static final int DOWN = 1;
	private static final int LEFT = 2;
	private static final int RIGHT = 3;
	
	public static final int ROW_CHANGED = 1;
	public static final int COL_CHANGED = 2;
	
	// use a matrix as the data model, but could also
	// be flattened to 1-D array along with the corresponding
	// implementation changes
	private SliderTile puzzleBoard[][];
	private int boardSize;
	private int numDirections;
	private boolean puzzleSolved;
	private SliderTile blankTile;
	
	
	public PuzzleBoard (int dim, SliderTile[] state) {
		if (dim > 0) { 
			boardSize = dim;
			puzzleBoard = new SliderTile[boardSize][boardSize];
			
			// generate new board
			if (state == null) {
				puzzleSolved = false;
				for (int i = 0; i < boardSize; i++) {
		        	for (int j = 0; j < boardSize; j++) {
		        		setTile(i, j, new SliderTile(i * boardSize + j, i, j));
		        	}
		        }
			// restore from saved state
			} else {
				for (int i = 0; i < boardSize; i++) {
		        	for (int j = 0; j < boardSize; j++) {
		        		setTile(i, j, state[i * boardSize + j]);
		        	}
		        }
			}
		}
	}
	
	public void shuffleBoard() {
		Random randGen = new Random();
		
		// randomly select a tile to be the blank tile
		blankTile = new SliderTile(-1, randGen.nextInt(boardSize), randGen.nextInt(boardSize));
		setTile(blankTile.row(), blankTile.col(), blankTile);
	
	    // Shuffle puzzle tiles in a deterministic way
		int numShuffles = boardSize*SHUFFLE_MULTIPLIER;
		for (int i = 0; i < numShuffles; i++) {
	        moveTile(randGen.nextInt(numDirections));
	    }
	}
	
	public boolean setTile (int row, int col, SliderTile tile) {
		if (row >= 0 && row < boardSize && col >= 0 && col < boardSize) {
			puzzleBoard[row][col] = tile;
			return true;
		} else {
			return false;
		}
	}
	
	public SliderTile getTile (int row, int col) {
		if (row >= 0 && row < boardSize && col >= 0 && col < boardSize) {
			return puzzleBoard[row][col];
		} else {
			return null;
		}
	}
	
	public SliderTile getBlankTile() {
		return blankTile;
	}
	
	public void swapTiles(int tile1, int tile2) {
    	
    	if (tile1 >= 0 && tile1 < getNumTiles() && tile2 >= 0 && tile2 < getNumTiles()) {
    		int tile1Row = tile1 / boardSize;
        	int tile1Col = tile1 % boardSize;
        	int tile2Row = tile2 / boardSize;
        	int tile2Col = tile2 % boardSize;
        	
        	// Log.i(LOG_TAG, "Swapping " + tile1Row + "," + tile1Col + " with " + tile2Row + "," + tile2Col);
        	
            SliderTile temp = getTile(tile1Row, tile1Col); 
     
            setTile(tile1Row, tile1Col, getTile(tile2Row, tile2Col));
            setTile(tile2Row, tile2Col, temp);
    	}
	}
			
	public int getTileLoc (SliderTile tile) {
		return tile.row() * boardSize + tile.col();
	}
	
	public int getBoardSize() {
		return boardSize;
	}
	
	public int getNumTiles() {
		return boardSize * boardSize;
	}
	
	public void setNumDirections(int num) {
		numDirections = num;
	}
	
    public boolean isTileMoveable(int[] loc) {
    	//Log.i(LOG_TAG, "isSelectable: index= " + loc[0] + "," + loc[1] + "  blankTile= " + blankTile.row() + "," + blankTile.col());
    	
    	// tile is moveable if it's in the same row or col as the empty tile
    	// and is not the empty tile itself
    	if (loc[0] == blankTile.row() || loc[1] == blankTile.col()) {
    		if (loc[0] * boardSize + loc[1] != getTileLoc(blankTile)) {
    			return true;
    		}
    	}
    	return false;
    }
	
    public boolean moveTile(int dir) {      
		SliderTile index;
		switch(dir) {
			case UP:
				//Log.i(LOG_TAG, "UP blankTile.row(): " + blankTile.row());
				if (blankTile.row() < boardSize - 1) {
					index = getTile(blankTile.row()+1, blankTile.col());
					updateTilesPlacement(index);
					return true;
				}
				return false;
			case DOWN:
				//Log.i(LOG_TAG, "DOWN blankTile.row(): " + blankTile.row());
				if (blankTile.row() > 0) {
					index = getTile(blankTile.row()-1, blankTile.col());
					updateTilesPlacement(index);
		 			return true;
				}
				return false;
			case LEFT:
				//Log.i(LOG_TAG, "LEFT blankTile.col(): " + blankTile.col());
				if (blankTile.col() < boardSize-1) {
					index = getTile(blankTile.row(), blankTile.col()+1);
					updateTilesPlacement(index);
		 			return true;
				}
				return false;
			case RIGHT:
				//Log.i(LOG_TAG, "RIGHT blankTile.col(): " + blankTile.col());
				if (blankTile.col() > 0) {
					index = getTile(blankTile.row(), blankTile.col()-1);
					updateTilesPlacement(index);
		 			return true;
				}
				return false;
		}
		return false;
    }
	  
    public int updateTilesPlacement(SliderTile dest) {
		//Log.i(LOG_TAG, " -------- update(): index: " + getTileLoc(dest) + "     blankIndex: " + getTileLoc(blankTile));
		if (dest.row() == blankTile.row()) {
			// single row of tiles being updated
			if (getTileLoc(blankTile) < getTileLoc(dest)) {
		        while (getTileLoc(blankTile) < getTileLoc(dest)) {
		            swapTiles(getTileLoc(blankTile), getTileLoc(blankTile) + 1);
		            blankTile.setCol(blankTile.col()+1);
		        }		
			} else {
		        while (getTileLoc(blankTile) > getTileLoc(dest) ) {
		        	swapTiles(getTileLoc(blankTile), getTileLoc(blankTile) - 1);   
		            blankTile.setCol(blankTile.col()-1);
		        }
			}
			return ROW_CHANGED;
		} else if (dest.col() == blankTile.col()){
			// single column of tiles being updated
			if (getTileLoc(blankTile) < getTileLoc(dest)) {
		        while (getTileLoc(blankTile) < getTileLoc(dest)) {
		        	swapTiles(getTileLoc(blankTile), getTileLoc(blankTile) + boardSize);
		            blankTile.setRow(blankTile.row()+1);
		        }		    			
			} else {
		        while (getTileLoc(blankTile) > getTileLoc(dest)) {
		        	swapTiles(getTileLoc(blankTile), getTileLoc(blankTile) - boardSize);
		            blankTile.setRow(blankTile.row()-1);
		        }			
			}
			return COL_CHANGED;
		}
		// nothing was updated
		return -1;
	}
    
    public boolean checkPuzzleSolved() {
    	
    	// check if each non-blank tile is in the right location
    	for (int i = 0; i < boardSize; i++) {
	    	for (int j = 0; j < boardSize; j++) {
	    		// don't check blank tile location
	    		if (puzzleBoard[i][j].num() >= 0) {
	    			// the tile num() is the ordered location
		    		if ((i * boardSize + j) - puzzleBoard[i][j].num() != 0) {
		    			Log.i(LOG_TAG, "row: " + i + "   col: " + j + " with tilenum: " + puzzleBoard[i][j].num() + " is not in order");
		    			return false;
		    		}
	    		}
	    	}
		}
    	// once a puzzle is solved, it stays solved
    	puzzleSolved = true;
    	return true;
    }
    
    public boolean getPuzzleSolvedState() {
    	return puzzleSolved;
    }
    
    // saving state when app is onPaused 
    public SliderTile[] serializeBoard() {
    	if (boardSize > 0) {
    		SliderTile[] boardState = new SliderTile[boardSize*boardSize];
    		
    		for (int i = 0; i < boardSize; i++) {
		    	for (int j = 0; j < boardSize; j++) {
		    		boardState[i * boardSize + j] = puzzleBoard[i][j];
		    	}
    		}
    		return boardState;
    	}
    	return null;
    }
    
}